package nr.co.mhgames.polyanim;

import java.awt.Color;
import java.util.ArrayList;

import nr.co.mhgames.polyanim.util.ColorDef;
import nr.co.mhgames.polyanim.util.Vector2f;
import nr.co.mhgames.polyanim.util.Vertex;


/**
 * A convex polygon. Polygon can contain three or more vertices. Each polygon
 * also has a color.
 * 
 * @author Mika Halttunen
 */
public class Polygon {
	/** Polygon color */
	protected final ColorDef color;

	/** Polygon vertices */
	protected final ArrayList<Vertex> vertices;

	/** Is the polygon convex? */
	protected boolean convex;

	private boolean convexDirty;

	/** Default constructor */
	public Polygon() {
		color = new ColorDef(255, 255, 255);
		vertices = new ArrayList<Vertex>();
		convex = true;
		convexDirty = true;
	}

	/**
	 * Constructor
	 * 
	 * @param vertices
	 *            List of vertices
	 */
	public Polygon(ArrayList<Vertex> vertices) {
		color = new ColorDef(255, 255, 255);
		this.vertices = vertices;
		convex = true;
		convexDirty = true;
	}

	/**
	 * Constructor
	 * 
	 * @param col
	 *            Polygon color
	 */
	public Polygon(ColorDef col) {
		this();
		color.setFrom(col);
	}

	/**
	 * Constructor
	 * 
	 * @param col
	 *            Polygon color
	 */
	public Polygon(Color col) {
		this();
		color.setFrom(col);
	}

	/**
	 * Add a vertex to the polygon.
	 * 
	 * @param v
	 *            Vertex to add
	 */
	public void addVertex(Vertex v) {
		vertices.add(v);
		convexDirty = true;
	}

	/**
	 * Add a vertex to the polygon.
	 * 
	 * @param x
	 *            Vertex X
	 * @param y
	 *            Vertex Y
	 */
	public void addVertex(float x, float y) {
		vertices.add(new Vertex(x, y));
		convexDirty = true;
	}

	/**
	 * Return the polygon vertices.
	 * 
	 * @return List of vertices
	 */
	public ArrayList<Vertex> getVertices() {
		convexDirty = true;
		return vertices;
	}

	/**
	 * Return the vertex at given index.
	 * 
	 * @param i
	 *            Vertex index
	 * @return Vertex at the index
	 */
	public Vertex getVertex(int i) {
		convexDirty = true;
		return vertices.get(i);
	}

	/**
	 * Set the given vertex to the specified index.
	 * 
	 * @param i
	 *            Vertex index
	 * @param v
	 *            New vertex
	 */
	public void setVertex(int i, Vertex v) {
		vertices.set(i, v);
		convexDirty = true;
	}

	/**
	 * Return the vertex count
	 * 
	 * @return Number of vertices
	 */
	public int getVertexCount() {
		return vertices.size();
	}


	/**
	 * Set the polygon color
	 * 
	 * @param c
	 *            New color
	 */
	public void setColor(ColorDef c) {
		color.setFrom(c);
	}

	/**
	 * Set the polygon color
	 * 
	 * @param c
	 *            New color
	 */
	public void setColor(Color c) {
		color.setFrom(c);
	}

	/**
	 * Set the polygon color
	 * 
	 * @param r
	 *            Red (0-255)
	 * @param g
	 *            Green (0-255)
	 * @param b
	 *            Blue (0-255)
	 * @param a
	 *            Alpha (0-255)
	 */
	public void setColor(int r, int g, int b, int a) {
		color.set(r, g, b, a);
	}

	/**
	 * Return the polygon color
	 * 
	 * @return Current color
	 */
	public ColorDef getColor() {
		return color;
	}


	/**
	 * Interpolate between two polygons: A and B. Note that the polygons must
	 * have exactly the same number of vertices.
	 * 
	 * @param a
	 *            Polygon A
	 * @param b
	 *            Polygon B
	 * @param t
	 *            Interpolation factor, between zero and one. Zero means the
	 *            resulting polygon is A, while one means that the resulting
	 *            polygon is B.
	 * @return An interpolated polygon between A and B.
	 */
	public static Polygon interpolate(Polygon a, Polygon b, float t) {
		assert (a.getVertexCount() == b.getVertexCount());
		if (a == b) {
			// It's the same polygon, don't interpolate
			return a;
		}

		if (t <= 0) {
			// Don't interpolate at all (result is A)
			return a;
		} else if (t >= 1.0f) {
			// Interpolate fully (result is B)
			return b;
		}

		// Reserve space for the new polygon
		ArrayList<Vertex> verts = new ArrayList<Vertex>(a.getVertexCount());
		Polygon res = new Polygon(verts);

		// Interpolate the color
		ColorDef cA = a.getColor();
		ColorDef cB = b.getColor();
		int rDiff = cB.red - cA.red;
		int gDiff = cB.green - cA.green;
		int bDiff = cB.blue - cA.blue;
		int aDiff = cB.alpha - cA.alpha;

		int nR = cA.red + (int) (rDiff * t);
		int nG = cA.green + (int) (gDiff * t);
		int nB = cA.blue + (int) (bDiff * t);
		int nA = cA.alpha + (int) (aDiff * t);
		res.color.set(nR, nG, nB, nA);

		// Check that the color is ok
		assert (res.color.red >= 0 && res.color.red < 256);
		assert (res.color.green >= 0 && res.color.green < 256);
		assert (res.color.blue >= 0 && res.color.blue < 256);
		assert (res.color.alpha >= 0 && res.color.alpha < 256);

		// Interpolate the vertices
		int vertexCount = a.getVertexCount();
		for (int i = 0; i < vertexCount; ++i) {
			Vertex vA = a.getVertex(i);
			Vertex vB = b.getVertex(i);

			float dX = vB.position.x - vA.position.x;
			float dY = vB.position.y - vA.position.y;
			res.vertices.add(new Vertex(vA.position.x + dX * t, vA.position.y + dY * t));
		}

		return res;
	}


	/**
	 * Test if the polygon vertices are ordered clockwise.
	 * 
	 * @return true if the vertex order is clockwise, false if it's
	 *         counter-clockwise
	 */
	public boolean isOrderedClockwise() {
		float area = 0;
		int verts = getVertexCount();

		for (int i = 0; i < verts; ++i) {
			Vector2f pos = getVertex(i).position;
			Vector2f pos2 = getVertex((i + 1) % verts).position;

			area += (pos.x * pos2.y - pos2.x * pos.y);
		}

		if (area > 0)
			return false;

		return true;
	}


	/**
	 * Test if the polygon is convex. Assumes simple polygons, as does
	 * everything else in PolyAnim.
	 * 
	 * @return true if the polygon is convex
	 */
	public boolean isConvex() {
		// We don't compute the test every time, but cache the result
		if (!convexDirty)
			return convex;
		else {
			boolean isPositive = false;
			int verts = getVertexCount();
			for (int i = 0; i < verts; ++i) {
				int lower = (i == 0) ? (verts - 1) : (i - 1);
				int middle = i;
				int upper = (i == verts - 1) ? (0) : (i + 1);

				Vector2f d0 = Vector2f.sub(vertices.get(middle).position,
						vertices.get(lower).position);
				Vector2f d1 = Vector2f.sub(vertices.get(upper).position,
						vertices.get(middle).position);
				float cross = d0.x * d1.y - d1.x * d0.y;

				// Cross product should have same sign for each vertex if the
				// polygon is convex.
				boolean newIsP = (cross >= 0) ? true : false;
				if (i == 0) {
					isPositive = newIsP;
				} else if (isPositive != newIsP) {
					convex = false;
					convexDirty = false;
					return false;
				}
			}

			convex = true;
			convexDirty = false;
			return true;
		}
	}


	@Override
	public String toString() {
		return String.format("Polygon: %d vertices, %s", getVertexCount(), super.toString());
	}
}
